﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OxyPlot;
using OxyPlot.Pdf;
using QstkCore.DataAccess;
using QstkCore.Domain;
using QstkCore.Plot.OxyPlot.CustomSeries;
using QstkCore.Util;

namespace QstkCore.Core
{
    public class EventProfiler
    {
        private readonly ConcurrentDictionary<string, IList<int>> eventMatrix;
//        private readonly DateTime startDay;
//        private readonly DateTime endDay;
        private readonly int lookbackDays;
        private readonly int lookforwardDays;
        private readonly int totalGraphDays;
        private readonly int marketDays;
        private readonly IList<string> symbols;
        private readonly MarketPrices close;
        private readonly Dictionary<string, IList<float>> dailyReturns;
        private Dictionary<string, IList<float>> marketNeutralDm;
        private List<float> studyStat;
        private List<float> studyStd;
        private IEnumerable<int> studyRange;

        public EventProfiler(ConcurrentDictionary<string, IList<int>> eventMatrix, DateTime startDay, DateTime endDay, int lookbackDays, int lookforwardDays)
        {
            this.eventMatrix = eventMatrix;
//            this.startDay = startDay;
//            this.endDay = endDay;
            this.lookbackDays = lookbackDays;
            this.lookforwardDays = lookforwardDays;

            symbols = eventMatrix.Keys.ToList();
            totalGraphDays = lookbackDays + lookforwardDays + 1;
            var dataObj = DataAccess.DataAccess.GetInstance();
            var timeOfDay = TimeSpan.FromHours(16);
            var timestamps = Nyse.GetTradingDates(startDay, endDay, timeOfDay);
            List<DateTime> timestamps1 = timestamps.ToList();
            close = dataObj.GetMarketData(symbols, "Close", new TimeSeries(timestamps1));
            marketDays = timestamps1.Count();

            close.ReplaceNanFill(FillDirection.Forward).ReplaceNanFill(FillDirection.Backward);
            dailyReturns = new Dictionary<string, IList<float>>();
            marketNeutralDm = new Dictionary<string, IList<float>>();
        }

        public void Study(string filename, bool plotErrorBars, bool plotMarketNeutral, bool plotEvents, string marketSymbol)
        {
            Console.WriteLine("Starting Event Study");

            // compute 0 centered daily returns
            foreach (var symbol in symbols)
            {
                var symList = close.ColumnList(symbol);
                dailyReturns[symbol] = symList.Returnize();
            }

            var marketList = close.ColumnList(marketSymbol);
            IList<float> marketDaily = marketList.Returnize();

            // make it market neutral
            if (plotMarketNeutral)
            {
                foreach (var symbol in symbols)
                {
                    marketNeutralDm[symbol] = new List<float>();
                    for (int i = 0; i < marketDays; i++)
                        marketNeutralDm[symbol].Add(dailyReturns[symbol][i] - marketDaily[i]);
                }

                // remove the market column from consideration
                marketNeutralDm.Remove(marketSymbol);
                IList<int> removedEventList;
                eventMatrix.TryRemove(marketSymbol, out removedEventList);
                symbols.Remove(marketSymbol);
            }
            else
            {
                marketNeutralDm = dailyReturns;
            }

            // Wipe out events which are on the boundary.
            Parallel.ForEach(symbols,
                             symbol =>
                             {
                                 foreach (int i in Enumerable.Range(0, lookbackDays))
                                     eventMatrix[symbol][i] = 0;

                                 foreach (int i in Enumerable.Range(marketDays - lookforwardDays, lookforwardDays))
                                     eventMatrix[symbol][i] = 0;
                             });

            // prepare to build impact matrix
            var returns = marketNeutralDm;
            int numCols = eventMatrix.Keys.Count;

            int numEvents = eventMatrix.Values.SelectMany(i => i).Sum();

            // create a blank impact matrix
            var impact = new double[totalGraphDays, numEvents];

            int currcol = 0;
            // step through each event in event matrix
            foreach (int column in Enumerable.Range(0, numCols))
            {
                string currSym = symbols[column];
                // search event days
                int eventDays = eventMatrix[currSym].Count;
                foreach (int eventIndex in Enumerable.Range(0, eventDays))
                {
                    int eventState = eventMatrix[symbols[column]][eventIndex];

                    // when we find an event
                    if (eventState == 1)
                    {
                        // copy the daily returns in to the impact matrix
                        int start = eventIndex - lookbackDays;
                        start = (start < 0) ? 0 : start;
                        int end = eventIndex + lookforwardDays + 1;

                        for (int i = start, j = 0; i < end && j < eventDays; i++, j++)
                        {
                            impact[j, currcol] = returns[currSym][i];
                        }

                        currcol = currcol + 1;
                    }
                }
            }

            // now compute cumulative daily returns
            Parallel.ForEach(Enumerable.Range(0, impact.GetLength(1)),
                             j =>
                             {
                                 foreach (int i in Enumerable.Range(0, impact.GetLength(0)))
                                     impact[i, j] = impact[i, j] + 1.0d;
                             });

            impact = impact.CumulativeProduct();

            Parallel.ForEach(ParallelEnumerable.Range(0, impact.GetLength(1)),
                             j =>
                             {
                                 var divisor = impact[0, j];
                                 foreach (int i in Enumerable.Range(0, impact.GetLength(0)))
                                 {
                                     impact[i, j] = impact[i, j] / divisor;
                                 }
                             });

            // normalize everything to the time of the event
            Parallel.ForEach(ParallelEnumerable.Range(0, impact.GetLength(1)),
                             j =>
                             {
                                 var divisor = impact[lookbackDays, j];
                                 foreach (int i in Enumerable.Range(0, impact.GetLength(0)))
                                     impact[i, j] = impact[i, j] / divisor;
                             });

            // prepare data for plot
            studyStat = new List<float>(impact.GetLength(0));
            studyStd = new List<float>(impact.GetLength(0));
            foreach (int i in Enumerable.Range(0, impact.GetLength(0)))
            {
                var row = new List<float>();
                for (int j = 0; j < impact.GetLength(1); j++)
                {
                    row.Add((float)impact[i, j]);
                }
                studyStat.Add(row.Average());
                studyStd.Add(row.StdDev());
            }

            studyRange = Enumerable.Range(-lookbackDays, (lookbackDays + lookforwardDays + 1));

            Plot(filename, impact, numEvents, plotMarketNeutral, plotEvents, plotErrorBars);
        }

        private void Plot(string filename, double[,] impact, int numEvents, bool plotMarketNeutral, bool plotEvents, bool plotErrorBars)
        {
            Console.WriteLine("Plotting Graph");

            var plotModel1 = new PlotModel();

            if (plotMarketNeutral)
            {
                plotModel1.Title = "market relative mean of " + numEvents + " events";
            }
            else
            {
                plotModel1.Title = "mean of " + numEvents + " events";
            }

            var horizAxis = new LinearAxis(AxisPosition.Bottom)
                                {
                                    Minimum = studyRange.Min() - 1,
                                    Maximum = studyRange.Max() + 1,
                                    Title = "Days"
                                };
            plotModel1.Axes.Add(horizAxis);

            var vertAxis = new LinearAxis(AxisPosition.Left)
                               {
                                   Minimum = Math.Min(studyStat.Min(), 0.5),
                                   Maximum = Math.Max(studyStat.Max(), 1.2),
                                   Title = "Cumulative Abnormal Returns"
                               };
            plotModel1.Axes.Add(vertAxis);

            var meanLineSeries = new LineSeries { Color = OxyColor.FromArgb(255, 00, 00, 255) };
            meanLineSeries.MarkerFill = meanLineSeries.Color;
            meanLineSeries.MarkerStroke = meanLineSeries.Color;
            meanLineSeries.MarkerType = MarkerType.Plus;
            meanLineSeries.StrokeThickness = 4;
            meanLineSeries.Title = "Mean";

            IEnumerator<int> x = studyRange.GetEnumerator();
            var y = studyStat.GetEnumerator();

            while (x.MoveNext() && y.MoveNext())
                meanLineSeries.Points.Add(new DataPoint(x.Current, y.Current));

            plotModel1.Series.Add(meanLineSeries);

            if (plotEvents)
            {
                // draw a line for each event
                var eventLineSeries = new LineSeries { Color = OxyColor.FromArgb(255, 255, 00, 00), StrokeThickness = 1 };

                x = studyRange.GetEnumerator();
                for (int i = 0; x.MoveNext() && i < impact.GetLength(0); i++)
                    foreach (int j in Enumerable.Range(0, impact.GetLength(1)))
                        eventLineSeries.Points.Add(new DataPoint(x.Current, impact[i, j]));
            }

            // draw a horizontal line at Y = 1.0
            var lineAnnotation = new LineAnnotation
                                     {
                                         Type = LineAnnotationType.Horizontal,
                                         Y = 1.0,
                                         Color = OxyColors.Black
                                     };
            plotModel1.Annotations.Add(lineAnnotation);

            if (plotErrorBars)
            {
                // draw errorbars if user wants them
                var errorSeries = new ErrorSeries();
                for (int i = lookbackDays; i < studyStd.Count; i++)
                {
                    errorSeries.Points.Add(new ErrorItem(i - lookbackDays, studyStat[i], 0, studyStd[i] * 2));
                    errorSeries.Color = OxyColor.FromArgb(255, 0xAA, 0xAA, 0xFF);
                    plotModel1.Series.Add(errorSeries);
                }
            }

            PdfExporter.Export(plotModel1, filename, 1000, 800);
        }
    }
}
